﻿# define _CRT_SECURE_NO_WARNINGS 1
//第六讲 函数
//1.函数的概念：其实在C语⾔也引⼊函数（function）的概念，有些翻译为：⼦程序，⼦程序这种翻译更加准确⼀些。
//C语⾔中的函数就是⼀个完成某项特定的任务的⼀⼩段代码
//分为库函数和自定义函数
//库函数举例;sqrt函数
//#include <stdio.h>
//#include <math.h>
//int main()
//{
//	double i = 16.0;
//	double r = sqrt(i);
//	printf("%lf", r);
//	return 0;
//}
//3.自定义函数
//语法形式：
//ret_type fun_name(形式参数)
//{
//
//}
//• ret_type 是函数返回类型
//• fun_name 是函数名
//• 括号中放的是形式参数
//•{}括起来的是函数体
//注意：
//• ret_type 是⽤来表⽰函数计算结果的类型，有时候返回类型可以是 void ，表⽰什么都不返回
//• fun_name 是为了⽅便使⽤函数；就像⼈的名字⼀样，有了名字⽅便称呼，函数有了名字⽅便调
//⽤，所以函数名尽量要根据函数的功能起的有意义。
//• 函数的参数就相当于，⼯⼚中送进去的原材料，函数的参数也可以是 void ，明确表⽰函数没有参
//数。如果有参数，要交代清楚参数的类型和名字，以及参数个数。
//•{}括起来的部分被称为函数体，函数体就是完成计算的过程
//3.2 函数的举例
//举个例⼦：
//写⼀个加法函数，完成2个整型变量的加法操作。
//#include <stdio.h>
//int Add(int x, int y)
//{
//	/*int z = 0;
//	z = x + y;*/
//	/*return z;*/
//	return x + y;
//}
//
//int main()
//{
//	int a = 0;
//	int b = 0;
//	scanf("%d %d", &a, &b);
//	int c = Add(a,b);
//	printf("%d\n", c);
//	return 0;
//}
//4. 形参和实参
//在函数使⽤的过程中，把函数的参数分为，实参和形参。
//再看看我们前⾯写的代码：
//4.1 实参
//在上⾯代码中，第2~7⾏是 Add 函数的定义，有了函数后，再第17⾏调⽤Add函数的。
//我们把第17⾏调⽤Add函数时，传递给函数的参数a和b，称为实际参数，简称实参。
//实际参数就是真实传递给函数的参数。
//4.2 形参
//在上⾯代码中，第2⾏定义函数的时候，在函数名 Add 后的括号中写的 x 和 y ，称为形式参数，简
//称形参。
//为什么叫形式参数呢？实际上，如果只是定义了 Add 函数，⽽不去调⽤的话， Add 函数的参数 x
//和 y 只是形式上存在的，不会向内存申请空间，不会真实存在的，所以叫形式参数。形式参数只有在
//函数被调⽤的过程中为了存放实参传递过来的值，才向内存申请空间，这个过程就是形参的实例化
//4.3 实参和形参的关系
//虽然我们提到了实参是传递给形参的，他们之间是有联系的，但是形参和实参各⾃是独⽴的内存空
//间。
//这个现象是可以通过调试来观察的。请看下⾯的代码和调试演⽰ :
//总的来说：形参是实参的临时拷贝
//5.return 语句
//在函数的设计中，函数中经常会出现return语句，这⾥讲⼀下return语句使⽤的注意事项
//• return后边可以是⼀个数值，也可以是⼀个表达式，如果是表达式则先执⾏表达式，再返回表达式
//的结果。
//• return后边也可以什么都没有，直接写 return; 这种写法适合函数返回类型是void的情况。
//• return语句执⾏后，函数就彻底返回，后边的代码不再执⾏。
//#include <stdio.h>
////int Add(int x, int y)
////{
////	/*int z = 0;
////	z = x + y;*/
////	/*return z;*/
////	return 0;
////	int z = 0;
////	z = x + y;
////}
////
////int main()
////{
////	int a = 0;
////	int b = 0;
////	scanf("%d %d", &a, &b);
////	int c = Add(a,b);
////	printf("%d\n", c);
////	return 0;
////}
//• return返回的值和函数返回类型不⼀致，系统会⾃动将返回的值隐式转换为函数的返回类型
//#include <stdio.h>
//int add(int x, int y)
//{
//	double z =  x + y;
//	return z;
//}
//int main()
//{
//	int a = 0;
//	int b = 0;
//	scanf("%d %d", &a, &b);
//	int c = add(a,b);
//	printf("%d\n", c);
//	return 0;
//}
//• 如果函数中存在if等分⽀的语句，则要保证每种情况下都有return返回，否则会出现编译错误。
//#include <stdio.h>
//test(x)
//{
//	if (x > 500)
//		return 1;
//	if (x > 1000);
//	else
//		return 0;
//}
//int main()
//{
//	int a = 0;
//	scanf("%d", &a);
//	test(a);
//	return 0;
//}
//• 函数的返回类型如果不写，编译器会默认函数的返回类型是int。
//• 函数写了返回类型，但是函数中没有使⽤return返回值，那么函数的返回值是未知的
/*6. 数组做函数参数
//如：写⼀个函数将⼀个整型数组的内容，全部置为 - 1，再写⼀个函数打印数组的内容*/
//一维数组：
//#include <stdio.h>
// void change(int arr2[],int sz)
//{
//	int i = 0;
//	for (i = 0; i < sz; i++)
//	{
//		arr2[i] = -1;
//	}
//}
//void print(int arr3[],int sz)
//{
//	int i = 0;
//	for (i = 0; i < sz; i++)
//	{
//		printf("%d", arr3[i]);
//	}
//}
//int main()
//{
//	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
//	int sz = sizeof(arr) / sizeof(arr[0]);
//	change(arr, sz);
//	print(arr,sz);
//	return 0;
//}
//如：写⼀个函数将⼀个整型数组的内容，全部置为 - 1，再写⼀个函数打印数组的内容 * /
//二维数组：
//#include <stdio.h>
// void change(int arr[3][5], int a, int b)
//{
//	 int i = 0;
//	 for (i = 0; i < a; i++)
//	 {
//		 int j = 0;
//		 for (j = 0; j < b; j++)
//		 {
//			 arr[i][j] = -1;
//		 }
//	 }
//}
// void print(int arr[3][5], int a, int b)
// {
//	 int i = 0;
//	 for (i = 0; i < a; i++)
//	 {
//		 int j = 0;
//		 for (j = 0; j < b; j++)
//		 {
//			 printf("%d ", arr[i][j]);
//		 }
//		 printf("\n");
//	 }
// }
//int main()
//{
//	int arr[3][5] = { {1,2,3},{2,3,4},{3,4,5} };
//	change(arr, 3, 5);
//	print(arr, 3, 5);
//	return 0;
////}
//7.嵌套调用和链式访问
//7.1 嵌套调⽤
//嵌套调⽤就是函数之间的互相调⽤，每个函数就像⼀个乐⾼零件，正是因为多个乐⾼的零件互相⽆缝
//的配合才能搭建出精美的乐⾼玩具，也正是因为函数之间有效的互相调⽤，最后写出来了相对⼤型的
//程序。
//假设我们计算某年某⽉有多少天？如果要函数实现，可以设计2个函数 : 
//#include <stdio.h>
//int is_leap_year(int year)
//{
//	if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))
//
//		return 1;
//	else
//	
//		return 0;
//}
//int  get_days_of_month(int year, int month)
//{
//	int arr[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
//	int day = arr[month];
//	if (is_leap_year(year) && month == 2)
//	{
//		day = day + 1;
//	}
//	return day;
//}
//int main()
//{
//	int year = 0;
//	int month = 0;
//	printf("请输入年份和月份：");
//	scanf("%d %d", &year, &month);
//	int day = get_days_of_month(year, month);
//	printf("%d", day);
//	return 0;
//}
//#include <stdio.h>
//#include <stdBool.h>
//bool is_leap_year(int year)
//{
//	if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))
//
//		return true;
//	else
//	
//		return false;
//}
//int main()
//{
//	int year = 0;
//	scanf("%d", &year);
//	if (is_leap_year(year))
//		printf("是闰年");
//	else
//		printf("不是闰年");
//}
//7.2 链式访问
//所谓链式访问就是将⼀个函数的返回值作为另外⼀个函数的参数，像链条⼀样将函数串起来就是函数
//的链式访问。
//⽐如：
//#include <stdio.h>
//int main()
//{
//	int len = strlen("abcdef");//1.strlen求⼀个字符串的⻓度 
//	printf("%d\n", len);//2.打印⻓度  
//	return 0;
//}
//#include <stdio.h>
//int main()
//{
//	printf("%d", printf("%d", printf("%d", 43)));
//	return 0;
//}
////输出结果是什么：4321、printf函数返回的是打印在屏幕上的字符的个数。
//上⾯的例⼦中，我们就第⼀个printf打印的是第⼆个printf的返回值，第⼆个printf打印的是第三个
//printf的返回值。
//第三个printf打印43，在屏幕上打印2个字符，再返回2
//第⼆个printf打印2，在屏幕上打印1个字符，再放回1
//第⼀个printf打印1
////所以屏幕上最终打印：4321
//8.函数的声明和定义
//8.1 单个⽂件⼀般我们在使⽤函数的时候，直接将函数写出来就使⽤了。
//⽐如：我们要写⼀个函数判断⼀年是否是闰年
//#include <stdio.h>
//#include <stdBool.h>
//bool is_leap_year(int year)函数的定义
//{
//	if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))
//
//		return true;
//	else
//	
//		return false;
//}
//int main()
//{
//	int year = 0;
//	scanf("%d", &year);
//	if (is_leap_year(year))函数的调用，定义在函数的定义前面，可以正常运行，如果放在后面呢
//		printf("是闰年");
//	else
//		printf("不是闰年");
//}
//下面将函数的定义放在函数的调用的后面
//#include <stdio.h>
//#include <stdBool.h>
//int main()
//{
//	int year = 0;
//	scanf("%d", &year);
//	if (is_leap_year(year))/*函数的调用，定义在函数的定义前面，可以正常运行，如果放在后面呢*/
//		printf("是闰年");
//	else
//		printf("不是闰年");
//}
//bool is_leap_year(int year)/*函数的定义*/
//{
//	if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))
//
//		return true;
//	else
//	
//		return false;
//}
//函数定义如果放在函数调用后面就会放出警告，说函数未定义这是因为C语⾔编译器对源代码进⾏编译的时候，从第⼀⾏往下扫描的，当遇到第7⾏的is_leap_year
//函数调⽤的时候，并没有发现前⾯有is_leap_year的定义，就报出了上述的警告。
//解决方法在前面对函数声明一下就欧克了
//#include <stdio.h>
//#include <stdBool.h>
//bool is_leap_year(int year);   /*函数的声明*/
//int main()
//{
//
//	int year = 0;
//	scanf("%d", &year);
//	if (is_leap_year(year))  /*函数的调用*/
//		printf("是闰年");
//	else
//		printf("不是闰年");
//}
//bool is_leap_year(int year) /*函数的定义*/
//{
//	if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))
//
//		return true;
//	else
//	
//		return false;
//}
//8.2 多个⽂件
//⼀般在企业中我们写代码时候，代码可能⽐较多，不会将所有的代码都放在⼀个⽂件中；我们往往会
//根据程序的功能，将代码拆分放在多个⽂件中。
//⼀般情况下，函数的声明、类型的声明放在头⽂件（.h）中，函数的实现是放在源⽂件（.c）⽂件中。
//#include "add.h"   /*自定义的头文件是用  #include "" 来包含的*/
//int main()
//{
//
//	int year = 0;
//	scanf("%d", &year);
//	if (is_leap_year(year))  /*函数的调用*/
//		printf("是闰年");
//	else
//		printf("不是闰年");
//}
//8.3 static和extern
//static 和extern 都是C语⾔中的关键字。
//static 是 静态的 的意思，可以⽤来：
//• 修饰局部变量
//• 修饰全局变量
//• 修饰函数
//extern 是⽤来声明外部符号的。在讲解 static 和 extern 之前再讲⼀下：作⽤域和⽣命周期。
//作⽤域（scope）是程序设计概念，通常来说，⼀段程序代码中所⽤到的名字并不总是有效（可⽤）
//的，⽽限定这个名字的可⽤性的代码范围就是这个名字的作⽤域。
//1. 局部变量的作⽤域是变量所在的局部范围。
//2. 全局变量的作⽤域是整个⼯程（项⽬）。
//⽣命周期指的是变量的创建(申请内存)到变量的销毁(收回内存)之间的⼀个时间段。
//1. 局部变量的⽣命周期是：进⼊作⽤域变量创建，⽣命周期开始，出作⽤域⽣命周期结束。
//2. 全局变量的⽣命周期是：整个程序的⽣命周期。
//8.3.1 static修饰局部变量
//static修饰局部变量改变了变量的⽣命周期，⽣命周期改变的本质是改变了变量的存储类型，本
//来⼀个局部变量是存储在内存的栈区的，但是被 static 修饰后存储到了静态区。存储在静态区的变
//量和全局变量是⼀样的，⽣命周期就和程序的⽣命周期⼀样了，只有程序结束，变量才销毁，内存才
//回收。但是作⽤域不变的。
//8.3.2 static修饰全局变量
//extern是⽤来声明外部符号的，如果⼀个全局的符号在A⽂件中定义的，在B⽂件中想使⽤，就可以使
//⽤ extern 进⾏声明，然后使⽤。
//代码1正常，代码2在编译的时候会出现链接性错误。
//结论：
//⼀个全局变量被static修饰，使得这个全局变量只能在本源⽂件内使⽤，不能在其他源⽂件内使⽤。
//本质原因是全局变量默认是具有外部链接属性的，在外部的⽂件中想使⽤，只要适当的声明就可以使
//⽤；但是全局变量被 static 修饰之后，外部链接属性就变成了内部链接属性，只能在⾃⼰所在的源
//⽂件内部使⽤了，其他源⽂件，即使声明了，也是⽆法正常使⽤的。
//使⽤建议：如果⼀个全局变量，只想在所在的源⽂件内部使⽤，不想被其他⽂件发现，就可以使⽤
//static 修饰。
//8.3.3 static修饰函数
//其实 static 修饰函数和 static 修饰全局变量是⼀模⼀样的，⼀个函数在整个⼯程都可以使⽤，
//被 static 修饰后，只能在本⽂件内部使⽤，其他⽂件⽆法正常的链接使⽤了。
//本质是因为函数默认是具有外部链接属性，具有外部链接属性，使得函数在整个⼯程中只要适当的声
//明就可以被使⽤。但是被 static 修饰后变成了内部链接属性，使得函数只能在⾃⼰所在源⽂件内部
//使⽤。
//使⽤建议：⼀个函数只想在所在的源⽂件内部使⽤，不想被其他源⽂件使⽤，就可以使⽤ static 修
//饰。
